Go Programming - Basics
Concepts of Programming Languages
Sebastian Macke
Rosenheim Technical University
Sebastian Macke
Rosenheim Technical University
-- Miro Board ....
2stackoverflow.com/questions/3272019/is-java-orthogonal
// Ints are allowed to return int returnint() { return 1; } // Structs are allowed to return typedef struct{} structdef; structdef returnstruct() { structdef mystruct; return mystruct; } // Arrays are not allowed to return int[3] returnarray() { static int myarray[3] = {1, 2, 3}; return myarray; }
Numbers and classes might be non-orthogonal in Java.
Integer a = 1000; Integer b = 1000; System.out.println(a == b); // false Integer a = 1; Integer b = 1; System.out.println(a == b); // true
-- Go doesn't allow this operation.
-- In Go, there is only one loop: for
int x; // x is a value MyClass y; // y is a reference
-- Go has pointers and values. But the way they are stored is the same.
8devblogs.microsoft.com/typescript/typescript-native-port/
11go env Print Go environment information
go run file.go just runs the code. Can be multiple files, too.
go build downloads dependencies and builds the executable, but needs a go.mod file.
go tool dist list list all supported platforms
env GOOS=darwin GOARCH=arm64 go build Build for Apple ARM CPU computers.
go fmt formats your files
go mod init creates a go.mod module file
go mod tidy updates the go mod file if you add more dependencies
12Go compiles fast
time ./make.bash Building Go cmd/dist using /opt/homebrew/Cellar/go/1.23.0/libexec. (go1.23.0 darwin/arm64) Building Go toolchain1 using /opt/homebrew/Cellar/go/1.23.0/libexec. Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1. Building Go toolchain2 using go_bootstrap and Go toolchain1. Building Go toolchain3 using go_bootstrap and Go toolchain2. Building packages and commands for darwin/arm64. --- Installed Go for .../go-src/go Installed commands in .../go-src/go/bin *** You need to add /Users/sebastian.macke/projects/QAware/vorlesung/go-src/go/bin to your PATH. ./make.bash 167,89s user 43,26s system 580% cpu 36,367 total
GCC C++ compile time: several hours
Rust: hours
Java compile time: several hours
package main import ( "fmt" ) func main() { fmt.Printf("Hello %s", "Programming with Go \xE2\x98\xAF\n") // \xE2\x98\xAF -> ☯ fmt.Printf("Hello %s", "Programming with Go ☯\n") }
Golang | Java | Comment |
---|---|---|
bool | boolean | |
string | String | |
int | machine-sized in Go | |
int8 | byte | |
int16 | short | |
int32 | int | |
int64 | long | |
uint | - | unsigned int |
uint8 | - | |
uint16 | - | |
uint32 | - | |
uint64 | - | |
uintptr | - | |
byte | - | alias for uint8 |
rune | char | alias for int32, represents a Unicode code point. In Java usually 2 bytes. |
float32 | float | |
float64 | double | |
complex64 // complex | ||
complex128 | ||
struct {x,z,y int} | class |
package main import ( "fmt" "math/cmplx" ) func main() { c := 3 + 4i fmt.Printf("c=%v\n", c) r, θ := cmplx.Polar(c) // Go supports variables with unicode characters fmt.Printf("r=%v, θ=%v\n", r, θ) }
Value types are primitive data types (int, double, boolean, etc.) that directly store their values in memory rather than referencing an object.
func foo(x int) { x = 2 } func bar(s string) { s = "world" } func main() { x := 1 foo(x) fmt.Println(x) // 1 s := "Hello" bar(s) fmt.Println(s) // Hello }
Languages differ of how serious they handle the type
Go uses a strong typing system. But why?
20int main() { signed int a = -1; unsigned int b = 0; if (a < b) { printf("a ís smaller than b\n"); } return 0; }
Also: Change int to char
21x = 10 # x is an int x = "hello" # now x is a str
---
def greet(**kwargs): for key, value in kwargs.items(): print(f"{key} = {value}") greet(name="Alice", age=30, city="Munich")
---
def dynamic(value): if value > 0: return "positive" else: return -1
package main import "fmt" func main() { var a int32 = -1 var b uint32 = 2 if a < int32(b) { fmt.Printf("a is smaller than b") } }
var foo int // default value zero foo = 32 var foo int = 32 var foo = 32 foo := 32 // infers (guesses) the type definition of type int foo := int(32) foo, ok := 32, true // foo is an int 32, ok is a boolean with value true
func PrintVariableDetails(v any) { typeof := reflect.TypeOf(v) fmt.Printf("The variable with type '%s' has the value '%v'\n", typeof.Name(), v) } func main() { var someValue any someValue = 2 PrintVariableDetails(someValue) someValue = "abcd" PrintVariableDetails(someValue) if tmp, ok := someValue.(string); ok { fmt.Println("someValue is a string and has the value", tmp) } }
package main import "fmt" func main() { m := make(map[string]string) // Initialize an empty map m["foo"] = "bar" // insert a key-value pair into map value, ok := m["asd"] // check if key is present, return parameters can be ignored with "_" fmt.Println(ok, value) // returns false, value is just an empty string "" value, ok = m["foo"] // returns true, value contains "bar" fmt.Println(ok, value) for k, v := range m { fmt.Println(k, v) } }
package main import "fmt" func main() { m1 := map[string]int{"x": 1, "y": 2} m2 := m1 // m2 points to the same map as m1 m2["x"] = 100 fmt.Println("m1:", m1) // map[x:100 y:2] fmt.Println("m2:", m2) // map[x:100 y:2] }
append()
)package main import "fmt" func main() { arr := [5]int{1, 2, 3, 4, 5} // create fixed array fmt.Println(arr) s := arr[0:2] // create a slice out of the array. Otherwise via "make([2]int)" for a new array fmt.Println(s) fmt.Println(len(s), cap(s)) // ==> 2, 5 s[0] = 100 // a slice is just a reference fmt.Println(arr) s = append(s, 8) s = append(s, 9) s = append(s, 10) s = append(s, 11) fmt.Println(len(s), cap(s)) // ==> 6, 10 fmt.Println(arr) }
// function taking an array func modifyArray(a [3]int) { a[0] = 99 // modifies only the local copy } // function taking a slice func modifySlice(s []int) { s[0] = 99 // modifies the underlying array } func main() { // Arrays arr := [3]int{1, 2, 3} modifyArray(arr) fmt.Println("Array after modifyArray:", arr) // [1 2 3] unchanged // Slices slice := []int{1, 2, 3} modifySlice(slice) fmt.Println("Slice after modifySlice:", slice) // [99 2 3] changed }
A pointer is a variable which contains a memory address
func main() { num := 42 // implicit type "int" with value 42 ptr := &num // implicit type of "pointer to type int points to num" //var num int = 42 // explicit type definition //var ptr *int = &num // explicit type definition fmt.Println("Value of num:", num) // Expected Output: Value of num: 42 fmt.Println("Address of num:", &num) // Expected Output: Address of num: 0xSOME_MEMORY_ADDRESS (this will vary every run) fmt.Println("Address stored in ptr:", ptr) // Expected Output: Address stored in ptr: 0xSOME_MEMORY_ADDRESS (same as address of num) fmt.Println("Value via pointer:", *ptr) // Expected Output: Value via pointer: 42 *ptr = 43 fmt.Println("Value of num:", num) // Expected output: New value in num = 43 }
func swap0(x, y int) (int, int) { return y, x }
func swap1(x, y int) { x, y = y, x }
func swap2(x *int, y *int) { *x, *y = *y, *x }
func swap3(x **int, y **int) { *x, *y = *y, *x }
func main() { var a, b = 1, 2 fmt.Printf("Initial : a=%d, b=%d\n", a, b) a, b = b, a fmt.Printf("After a,b = b,a : a=%d, b=%d\n", a, b) swap0(a, b) fmt.Printf("After swap0(a,b) : a=%d, b=%d\n", a, b) a, b = swap0(a, b) fmt.Printf("After a,b = swap0(a,b) : a=%d, b=%d\n", a, b) swap1(a, b) fmt.Printf("After swap1(a,b) : a=%d, b=%d\n", a, b) swap2(&a, &b) fmt.Printf("After swap2(&a,&b) : a=%d, b=%d\n", a, b) pa, pb := &a, &b swap3(&pa, &pb) fmt.Printf("After swap3(&pa, &pb): a=%d, b=%d, *pa=%v, *pb=%v\n", a, b, *pa, *pb) }
Pointer arithmetic is a "DON'T DO" in Go!
// IsPalindrome implementation. Does only work for 1-Byte UTF-8 chars (ASCII). func IsPalindrome(word string) bool { for pos := 0; pos < len(word)/2; pos++ { if word[pos] != word[len(word)-pos-1] { return false } } return true }
// palindrome_test.go func TestPalindrome(t *testing.T) { if !IsPalindrome("") { t.Error("isPalindrome('' should be true. But is false.") } if !IsPalindrome("o") { t.Error("isPalindrome('o' should be true. But is false.") } if !IsPalindrome("oto") { t.Error("isPalindrome('oto' should be true. But is false.") } if IsPalindrome("ottos") { t.Error("isPalindrome('ottos' should be false. But is true.") } }
// IsPalindrome2 is using runes. This works for all UTF-8 chars (SBC, MBC). func IsPalindrome2(word string) bool { var runes = []rune(word) for pos, ch := range runes { if ch != runes[len(runes)-pos-1] { return false } } return true }
[]rune(string)
converts a string into a slice of runespos, ch := range runes
// IsPalindrome3 is implemented by reusing Reverse(). Reverse works for UTF-8 chars. func IsPalindrome3(word string) bool { return strings.Reverse(word) == word }
github.com/s-macke/concepts-of-programming-languages/blob/master/docs/exercises/Exercise2.1.md
func main() { z := [2]int64{10, 20} // fixed size array y := &z[0] // pointer to first entry in array fmt.Println(y) y = (*int64)(unsafe.Add(unsafe.Pointer(y), 8)) // 64 Bit has 8 bytes fmt.Println(y) *y = 30 // changes second parameter in the array z fmt.Println(z) }
Types can be defined with the keyword "type"
// Page contains an array of words. type Page []string // Book is an array of pages. type Book []Page // Index contains a list of pages for each word in a book. type Index map[string][]int // MakeIndex generates an index structure func MakeIndex(book Book) Index { .... }
import ( "flag" "fmt" ) // Simple test for the Go flag API. func main() { // construct a string flag with a default ip address and a description. ip := flag.String("ip", "192.168.1.1", "Overrides the default IP address.") port := flag.String("port", "8080", "Overrides the default listen port.") flag.Parse() fmt.Println("\nDefault value for IP: " + *ip) fmt.Println("\nDefault value for port: " + *port) }
github.com/s-macke/concepts-of-programming-languages/blob/master/docs/exercises/Exercise2.2.md
46